package com.smile.frame.workflow.flowable.demo.testFlowable;

import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.smile.frame.workflow.flowable.demo.dto.BpmModelCreateReqDTO;
import com.smile.frame.workflow.flowable.demo.dto.BpmModelMetaInfoRespDTO;
import com.smile.frame.workflow.flowable.demo.dto.BpmModelUpdateReqDTO;
import com.smile.frame.workflow.flowable.demo.constants.BpmnModelConstants;
import com.smile.frame.workflow.flowable.demo.util.BpmnModelUtils;
import com.smile.frame.workflow.flowable.demo.util.ValidationUtils;
import lombok.extern.slf4j.Slf4j;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.bpmn.model.StartEvent;
import org.flowable.bpmn.model.UserTask;
import org.flowable.common.engine.impl.db.SuspensionState;
import org.flowable.engine.*;
import org.flowable.engine.history.HistoricActivityInstance;
import org.flowable.engine.repository.Deployment;
import org.flowable.engine.repository.Model;
import org.flowable.engine.repository.ProcessDefinition;
import org.flowable.engine.runtime.ProcessInstance;
import org.flowable.task.api.Task;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;

import javax.annotation.Resource;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;


/**
 * @author LGC
 */
@Slf4j
@SpringBootTest
class WorkflowFlowableApplicationTest {

    /**
     * 新建模型-> 模型部署（生成流程定义）-> 根据流程定义开启流程
     */

    @Resource
    private RepositoryService repositoryService;
    @Resource
    private IdentityService identityService;
    @Autowired
    private RuntimeService runtimeService;
    @Autowired
    private TaskService taskService;
    @Autowired
    private HistoryService historyService;


    private void saveModelBpmnXml(Model model, String bpmnXml) {
        if (StrUtil.isEmpty(bpmnXml)) {
            return;
        }
        repositoryService.addModelEditorSource(model.getId(), StrUtil.utf8Bytes(bpmnXml));
    }

    private BpmModelMetaInfoRespDTO buildMetaInfo(Model model) {
        return JSONUtil.toBean(model.getMetaInfo(), BpmModelMetaInfoRespDTO.class);
    }

    private void copyToCreateModel(Model model, BpmModelCreateReqDTO bean) {
        model.setName(bean.getName());
        model.setKey(bean.getKey());
        model.setCategory(bean.getCategory());
        model.setMetaInfo(buildMetaInfoStr(null, bean.getDescription()));
    }

    private void copyToUpdateModel(Model model, BpmModelUpdateReqDTO bean) {
        model.setName(bean.getName());
        model.setCategory(bean.getCategory());
        model.setMetaInfo(buildMetaInfoStr(buildMetaInfo(model), bean.getDescription()));
    }

    private String buildMetaInfoStr(BpmModelMetaInfoRespDTO metaInfo, String description) {
        if (metaInfo == null) {
            metaInfo = new BpmModelMetaInfoRespDTO();
        }
        if (StrUtil.isNotEmpty(description)) {
            metaInfo.setDescription(description);
        }
        return JSONUtil.toJsonStr(metaInfo);
    }


    private String createModel(BpmModelCreateReqDTO createReqVO, String bpmnXml) {
        log.info("start创建模型");
        if (!ValidationUtils.isXmlNCName(createReqVO.getKey())) {
            log.error("流程标识格式不正确，需要以字母或下划线开头，后接任意字母、数字、中划线、下划线、句点！");
            return null;
        }
        Model keyModel = repositoryService.createModelQuery().modelKey(createReqVO.getKey()).singleResult();
        if (keyModel != null) {
            log.error("校验流程标识已经存在");
            return null;
        }
        // 创建流程定义
        Model model = repositoryService.newModel();
        copyToCreateModel(model, createReqVO);
        // 租户
        model.setTenantId("");
        // 保存流程定义
        repositoryService.saveModel(model);
        // 保存 BPMN XML
        saveModelBpmnXml(model, bpmnXml);
        String modelId = model.getId();
        log.info("end创建模型");
        return modelId;
    }

    private void updateModel(BpmModelUpdateReqDTO updateReqVO) {
        log.info("start更新模型");
        // 校验流程模型存在
        Model model = repositoryService.getModel(updateReqVO.getId());
        if (model == null) {
            log.error("流程模型不存在");
            throw new RuntimeException("流程模型不存在");
        }
        // 修改流程定义
        copyToUpdateModel(model, updateReqVO);
        // 更新模型
        repositoryService.saveModel(model);
        // 更新 BPMN XML
        saveModelBpmnXml(model, updateReqVO.getBpmnXml());
        log.info("end更新模型");
    }


    private void validateBpmnXml(byte[] bpmnBytes) {
        BpmnModel bpmnModel = BpmnModelUtils.getBpmnModel(bpmnBytes);
        if (bpmnModel == null) {
            log.error("流程模型不存在");
            throw new RuntimeException("流程模型不存在");
        }
        // 1. 没有 StartEvent
        StartEvent startEvent = BpmnModelUtils.getStartEvent(bpmnModel);
        if (startEvent == null) {
            log.error("部署流程失败，原因：BPMN 流程图中，没有开始事件");
            throw new RuntimeException("部署流程失败，原因：BPMN 流程图中，没有开始事件");
        }
        // 2. 校验 UserTask 的 name 都配置了
        List<UserTask> userTasks = BpmnModelUtils.getBpmnModelElements(bpmnModel, UserTask.class);
        userTasks.forEach(userTask -> {
            if (StrUtil.isEmpty(userTask.getName())) {
                log.error(String.format("部署流程失败，原因：BPMN 流程图中，用户任务(%s)的名字不存在", userTask.getId()));
                throw new RuntimeException(String.format("部署流程失败，原因：BPMN 流程图中，用户任务(%s)的名字不存在", userTask.getId()));
            }
        });
    }


    public String createProcessDefinition(Model model, byte[] bpmnBytes) {
        // 创建 Deployment 部署
        log.info("创建 Deployment 部署 并创建实例");
        Deployment deploy = repositoryService.createDeployment()
                .key(model.getKey()).name(model.getName()).category(model.getCategory())
                .addBytes(model.getKey() + BpmnModelConstants.BPMN_FILE_SUFFIX, bpmnBytes)
                .tenantId("")
                .disableSchemaValidation() // 禁用 XML Schema 验证，因为有自定义的属性
                .deploy();

        log.info("设置 ProcessDefinition 的 category 分类");
        ProcessDefinition definition = repositoryService.createProcessDefinitionQuery()
                .deploymentId(deploy.getId()).singleResult();
        // 设置 ProcessDefinition 的 category 分类
        repositoryService.setProcessDefinitionCategory(definition.getId(), model.getCategory());
        // 注意 1，ProcessDefinition 的 key 和 name 是通过 BPMN 中的 <bpmn2:process /> 的 id 和 name 决定
        // 注意 2，目前该项目的设计上，需要保证 Model、Deployment、ProcessDefinition 使用相同的 key，保证关联性。
        //          否则，会导致 ProcessDefinition 的分页无法查询到。
        if (!Objects.equals(definition.getKey(), model.getKey())) {
//            流程定义的key期望是({})，当前是({})，请修改 BPMN 流程图
//            throw exception(PROCESS_DEFINITION_KEY_NOT_MATCH, model.getKey(), definition.getKey());
        }
        if (!Objects.equals(definition.getName(), model.getName())) {
//            流程定义的name期望是({})，当前是({})，请修改 BPMN 流程图
//            throw exception(PROCESS_DEFINITION_NAME_NOT_MATCH, model.getName(), definition.getName());
        }

        return definition.getId();
    }

    /**
     * 挂起 deploymentId 对应的流程定义
     * <p>
     * 注意：这里一个 deploymentId 只关联一个流程定义
     *
     * @param deploymentId 流程发布Id
     */
    private void updateProcessDefinitionSuspended(String deploymentId) {
        if (StrUtil.isEmpty(deploymentId)) {
            return;
        }
        ProcessDefinition oldDefinition = repositoryService.createProcessDefinitionQuery().deploymentId(deploymentId).singleResult();
        if (oldDefinition == null) {
            return;
        }
        updateProcessDefinitionState(oldDefinition.getId(), SuspensionState.SUSPENDED.getStateCode());
    }

    public void updateProcessDefinitionState(String id, Integer state) {
        // 激活
        if (Objects.equals(SuspensionState.ACTIVE.getStateCode(), state)) {
            repositoryService.activateProcessDefinitionById(id, false, null);
            return;
        }
        // 挂起
        if (Objects.equals(SuspensionState.SUSPENDED.getStateCode(), state)) {
            // suspendProcessInstances = false，进行中的任务，不进行挂起。
            // 原因：只要新的流程不允许发起即可，老流程继续可以执行。
            repositoryService.suspendProcessDefinitionById(id, false, null);
            return;
        }
        log.error("[updateProcessDefinitionState][流程定义({}) 修改未知状态({})]", id, state);
    }

    public void deployModel(String modelId) {
        log.info("start部署模型");
        // 1.1 校验流程模型存在
        Model model = repositoryService.getModel(modelId);
        if (ObjectUtils.isEmpty(model)) {
            log.error("流程模型不存在");
            return;
        }
        // 1.2 校验流程图
        byte[] bpmnBytes = repositoryService.getModelEditorSource(model.getId());
        validateBpmnXml(bpmnBytes);
        // 1.3 校验表单已配
//        BpmModelMetaInfoRespDTO metaInfo = JSONUtil.toBean(model.getMetaInfo(), BpmModelMetaInfoRespDTO.class);
//        BpmFormDO form = validateFormConfig(metaInfo);
        // 1.4 校验任务分配规则已配置
//        taskCandidateInvoker.validateBpmnConfig(bpmnBytes);
//        validateBpmnConfig(bpmnBytes);

        // 2.1 创建流程定义
        log.info("创建流程定义");
//        String definitionId = processDefinitionService.createProcessDefinition(model, metaInfo, bpmnBytes, form);
        String definitionId = createProcessDefinition(model, bpmnBytes);

        // 2.2 将老的流程定义进行挂起。也就是说，只有最新部署的流程定义，才可以发起任务。
        log.info("将老的流程定义进行挂起。也就是说，只有最新部署的流程定义，才可以发起任务。");
        updateProcessDefinitionSuspended(model.getDeploymentId());

        // 2.3 更新 model 的 deploymentId，进行关联
        log.info("更新 model 的 deploymentId，进行关联");
        ProcessDefinition definition = repositoryService.getProcessDefinition(definitionId);
        model.setDeploymentId(definition.getDeploymentId());
        repositoryService.saveModel(model);
        log.info("end部署模型");
    }


    public void deleteModel(String modelId) {
        // 校验流程模型存在
        Model model = repositoryService.getModel(modelId);
        if (ObjectUtils.isEmpty(model)) {
            log.error("流程模型不存在");
            return;
        }
        // 执行删除
        log.info("执行删除");
        repositoryService.deleteModel(modelId);
        // 禁用流程定义
        log.info("禁用流程定义");
        updateProcessDefinitionSuspended(model.getDeploymentId());
    }


    /**
     * 创建模型
     */
    @Test
    public void createModelTest() {
        BpmModelCreateReqDTO reqVO = new BpmModelCreateReqDTO();
        reqVO.setKey("test");
        reqVO.setName("测试流程");
        reqVO.setCategory("测试");
        reqVO.setDescription("测试流程");
        String bpmnXml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
                "<bpmn2:definitions xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:bpmn2=\"http://www.omg.org/spec/BPMN/20100524/MODEL\" xmlns:bpmndi=\"http://www.omg.org/spec/BPMN/20100524/DI\" xmlns:dc=\"http://www.omg.org/spec/DD/20100524/DC\" xmlns:di=\"http://www.omg.org/spec/DD/20100524/DI\" id=\"diagram_Process_1657960588001\" targetNamespace=\"http://flowable.org/bpmn\"><bpmn2:process id=\"test\" name=\"测试流程\" isExecutable=\"true\"><bpmn2:startEvent id=\"Event_1cdh08d\"><bpmn2:outgoing>Flow_0ski964</bpmn2:outgoing></bpmn2:startEvent><bpmn2:userTask id=\"task01\" name=\"领导审批\"><bpmn2:incoming>Flow_0ski964</bpmn2:incoming><bpmn2:outgoing>Flow_04zh2qw</bpmn2:outgoing></bpmn2:userTask><bpmn2:sequenceFlow id=\"Flow_0ski964\" sourceRef=\"Event_1cdh08d\" targetRef=\"task01\" /><bpmn2:userTask id=\"task02\" name=\"HR 审批\"><bpmn2:incoming>Flow_04zh2qw</bpmn2:incoming><bpmn2:outgoing>Flow_0tc28rh</bpmn2:outgoing></bpmn2:userTask><bpmn2:sequenceFlow id=\"Flow_04zh2qw\" sourceRef=\"task01\" targetRef=\"task02\" /><bpmn2:endEvent id=\"Event_071a8ic\"><bpmn2:incoming>Flow_0tc28rh</bpmn2:incoming></bpmn2:endEvent><bpmn2:sequenceFlow id=\"Flow_0tc28rh\" sourceRef=\"task02\" targetRef=\"Event_071a8ic\" /></bpmn2:process><bpmndi:BPMNDiagram id=\"BPMNDiagram_1\"><bpmndi:BPMNPlane id=\"test_di\" bpmnElement=\"test\"><bpmndi:BPMNEdge id=\"Flow_0ski964_di\" bpmnElement=\"Flow_0ski964\"><di:waypoint x=\"278\" y=\"330\" /><di:waypoint x=\"360\" y=\"330\" /></bpmndi:BPMNEdge><bpmndi:BPMNEdge id=\"Flow_04zh2qw_di\" bpmnElement=\"Flow_04zh2qw\"><di:waypoint x=\"460\" y=\"330\" /><di:waypoint x=\"530\" y=\"330\" /></bpmndi:BPMNEdge><bpmndi:BPMNEdge id=\"Flow_0tc28rh_di\" bpmnElement=\"Flow_0tc28rh\"><di:waypoint x=\"630\" y=\"330\" /><di:waypoint x=\"692\" y=\"330\" /></bpmndi:BPMNEdge><bpmndi:BPMNShape id=\"Event_1cdh08d_di\" bpmnElement=\"Event_1cdh08d\"><dc:Bounds x=\"242\" y=\"312\" width=\"36\" height=\"36\" /></bpmndi:BPMNShape><bpmndi:BPMNShape id=\"task01_di\" bpmnElement=\"task01\"><dc:Bounds x=\"360\" y=\"290\" width=\"100\" height=\"80\" /></bpmndi:BPMNShape><bpmndi:BPMNShape id=\"task02_di\" bpmnElement=\"task02\"><dc:Bounds x=\"530\" y=\"290\" width=\"100\" height=\"80\" /></bpmndi:BPMNShape><bpmndi:BPMNShape id=\"Event_071a8ic_di\" bpmnElement=\"Event_071a8ic\"><dc:Bounds x=\"692\" y=\"312\" width=\"36\" height=\"36\" /></bpmndi:BPMNShape></bpmndi:BPMNPlane></bpmndi:BPMNDiagram></bpmn2:definitions>";
        createModel(reqVO, bpmnXml);
    }

    /**
     * 更新模型
     */
    @Test
    public void updateModelTest() {
        BpmModelUpdateReqDTO reqVO = new BpmModelUpdateReqDTO();
        reqVO.setId("e79c0340-33a0-11ef-aa02-00ff0e3fbc3b");
        reqVO.setName("测试流程");
        reqVO.setCategory("测试");
        reqVO.setDescription("测试流程");

        reqVO.setBpmnXml("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
                "<bpmn2:definitions xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:bpmn2=\"http://www.omg.org/spec/BPMN/20100524/MODEL\" xmlns:bpmndi=\"http://www.omg.org/spec/BPMN/20100524/DI\" xmlns:dc=\"http://www.omg.org/spec/DD/20100524/DC\" xmlns:di=\"http://www.omg.org/spec/DD/20100524/DI\" id=\"diagram_Process_1657960588001\" targetNamespace=\"http://flowable.org/bpmn\"><bpmn2:process id=\"test\" name=\"测试流程\" isExecutable=\"true\"><bpmn2:startEvent id=\"Event_1cdh08d\"><bpmn2:outgoing>Flow_0ski964</bpmn2:outgoing></bpmn2:startEvent><bpmn2:userTask id=\"task01\" name=\"领导审批\"><bpmn2:incoming>Flow_0ski964</bpmn2:incoming><bpmn2:outgoing>Flow_04zh2qw</bpmn2:outgoing></bpmn2:userTask><bpmn2:sequenceFlow id=\"Flow_0ski964\" sourceRef=\"Event_1cdh08d\" targetRef=\"task01\" /><bpmn2:userTask id=\"task02\" name=\"HR 审批\"><bpmn2:incoming>Flow_04zh2qw</bpmn2:incoming><bpmn2:outgoing>Flow_0tc28rh</bpmn2:outgoing></bpmn2:userTask><bpmn2:sequenceFlow id=\"Flow_04zh2qw\" sourceRef=\"task01\" targetRef=\"task02\" /><bpmn2:endEvent id=\"Event_071a8ic\"><bpmn2:incoming>Flow_0tc28rh</bpmn2:incoming></bpmn2:endEvent><bpmn2:sequenceFlow id=\"Flow_0tc28rh\" sourceRef=\"task02\" targetRef=\"Event_071a8ic\" /></bpmn2:process><bpmndi:BPMNDiagram id=\"BPMNDiagram_1\"><bpmndi:BPMNPlane id=\"test_di\" bpmnElement=\"test\"><bpmndi:BPMNEdge id=\"Flow_0ski964_di\" bpmnElement=\"Flow_0ski964\"><di:waypoint x=\"278\" y=\"330\" /><di:waypoint x=\"360\" y=\"330\" /></bpmndi:BPMNEdge><bpmndi:BPMNEdge id=\"Flow_04zh2qw_di\" bpmnElement=\"Flow_04zh2qw\"><di:waypoint x=\"460\" y=\"330\" /><di:waypoint x=\"530\" y=\"330\" /></bpmndi:BPMNEdge><bpmndi:BPMNEdge id=\"Flow_0tc28rh_di\" bpmnElement=\"Flow_0tc28rh\"><di:waypoint x=\"630\" y=\"330\" /><di:waypoint x=\"692\" y=\"330\" /></bpmndi:BPMNEdge><bpmndi:BPMNShape id=\"Event_1cdh08d_di\" bpmnElement=\"Event_1cdh08d\"><dc:Bounds x=\"242\" y=\"312\" width=\"36\" height=\"36\" /></bpmndi:BPMNShape><bpmndi:BPMNShape id=\"task01_di\" bpmnElement=\"task01\"><dc:Bounds x=\"360\" y=\"290\" width=\"100\" height=\"80\" /></bpmndi:BPMNShape><bpmndi:BPMNShape id=\"task02_di\" bpmnElement=\"task02\"><dc:Bounds x=\"530\" y=\"290\" width=\"100\" height=\"80\" /></bpmndi:BPMNShape><bpmndi:BPMNShape id=\"Event_071a8ic_di\" bpmnElement=\"Event_071a8ic\"><dc:Bounds x=\"692\" y=\"312\" width=\"36\" height=\"36\" /></bpmndi:BPMNShape></bpmndi:BPMNPlane></bpmndi:BPMNDiagram></bpmn2:definitions>");
        updateModel(reqVO);
    }

    @Test
    public void deployModelTest() {
        deployModel("d69e46a3-348b-11ef-91ee-00ff0e3fbc3b");
    }

    @Test
    public void deleteModelTest() {
        deleteModel("e79c0340-33a0-11ef-aa02-00ff0e3fbc3b");
    }


    @Test
    public void createTest1ProcessInstance() {
        // 参数
        Map<String, Object> variables = new HashMap<>();
        // 流程实例状态：审批中
        variables.put("manager", "100");
        variables.put("hr", "200");

//        Authentication.setAuthenticatedUserId("123");ACT_HI_VARINST
        identityService.setAuthenticatedUserId("123");
        ProcessInstance instance = runtimeService.startProcessInstanceByKey("test1", "11", variables);
        log.debug("流程实例ID:{},流程定义ID:{},业务标识:{}", instance.getId(), instance.getProcessDefinitionId(), instance.getBusinessKey());

//        ProcessInstance instance = runtimeService.createProcessInstanceBuilder()
//                .processDefinitionId("test1:1:30f3b520-3484-11ef-956a-00ff0e3fbc3b")
//                .processDefinitionKey("test")
//                .businessKey("业务key")
//                .name("测试流程")
//                .variables(variables)
//                .start();
    }

    /***********************************************/

    @Test
    void sub() {
        // 员工提交请假申请
        Map<String, Object> map = new HashMap<>();
        map.put("day", 5);
        map.put("studentUser", "小明");
        // leave为员工请假流程xml文件中的id
        ProcessInstance a1 = runtimeService.startProcessInstanceByKey("leave", map);


        Task task = taskService.createTaskQuery().processInstanceId(a1.getId()).singleResult();
        taskService.complete(task.getId());
    }


    @Test
    void queryLeadTask() {
        // 查询领导分组的任务
        List<Task> teacher = taskService.createTaskQuery().taskCandidateGroup("a").list();
        for (Task task : teacher) {
            // 根据任务id查询当前任务参数
            Map<String, Object> variables = taskService.getVariables(task.getId());
            System.out.println(variables.get("day") + "," + variables.get("studentUser"));
        }
    }

    @Test
    void queryBossTask() {
        // 查询老板分组的任务
        List<Task> teacher = taskService.createTaskQuery().taskCandidateGroup("b").list();
        for (Task task : teacher) {
            // 根据任务id查询当前任务参数
            Map<String, Object> variables = taskService.getVariables(task.getId());
            System.out.println(variables.get("day") + "," + variables.get("studentUser"));
        }
    }

    @Test
    void LeadApprovalTask() {
        // 领导审批
        List<Task> teacherTaskList = taskService.createTaskQuery().taskCandidateGroup("a").list();
        Map<String, Object> teacherMap = new HashMap<>();
        teacherMap.put("outcome", "通过");
        for (Task teacherTask : teacherTaskList) {
            taskService.complete(teacherTask.getId(), teacherMap);
        }
    }

    @Test
    void boosApprovalTask() {
        // 老板审批
        List<Task> teacherTaskList = taskService.createTaskQuery().taskCandidateGroup("b").list();
        Map<String, Object> teacherMap = new HashMap<>();
        teacherMap.put("outcome", "通过");
        for (Task teacherTask : teacherTaskList) {
            taskService.complete(teacherTask.getId(), teacherMap);
        }
    }

    @Test
    void queryHistory() {
        List<ProcessInstance> processInstance = runtimeService.createProcessInstanceQuery().processDefinitionKey("leave").orderByStartTime().desc().list();
        if (CollectionUtils.isEmpty(processInstance)) {
            System.out.println("------------------------------------------");
            return;
        }
        // 获取最近的一个流程
        List<HistoricActivityInstance> activities = historyService.createHistoricActivityInstanceQuery()
                .processInstanceId(processInstance.get(0).getId())
                // 只查询已经完成的活动
                .finished()
                // 按照结束时间排序
                .orderByHistoricActivityInstanceEndTime().desc()
                .list();
        List<String> collect = activities.stream().map(a -> "活动名称:" + a.getActivityName() + ";活动执行时间:" + a.getDurationInMillis() + "毫秒").collect(Collectors.toList());
        for (String s : collect) {
            System.out.println(s);
        }
    }

    @Test
    void Test() {
        // 发起请假
        Map<String, Object> map = new HashMap<>();
        map.put("day", 5);
        map.put("studentUser", "小明");
        ProcessInstance studentLeave = runtimeService.startProcessInstanceByKey("leave", map);
        Task task = taskService.createTaskQuery().processInstanceId(studentLeave.getId()).singleResult();
        taskService.complete(task.getId());

        // 领导审批
        List<Task> teacherTaskList = taskService.createTaskQuery().taskCandidateGroup("a").list();
        Map<String, Object> teacherMap = new HashMap<>();
        teacherMap.put("outcome", "通过");
        for (Task teacherTask : teacherTaskList) {
            taskService.complete(teacherTask.getId(), teacherMap);
        }

        // 老板审批
        List<Task> principalTaskList = taskService.createTaskQuery().taskCandidateGroup("b").list();
        Map<String, Object> principalMap = new HashMap<>();
        principalMap.put("outcome", "通过");
        for (Task principalTask : principalTaskList) {
            taskService.complete(principalTask.getId(), principalMap);
        }

        // 查看历史
        List<HistoricActivityInstance> activities = historyService.createHistoricActivityInstanceQuery()
                .processInstanceId(studentLeave.getId())
                .finished()
                .orderByHistoricActivityInstanceEndTime().asc()
                .list();
        for (HistoricActivityInstance activity : activities) {
            System.out.println(activity.getActivityName());
        }
    }
}